/*-------------------------------------------------------------------------
 *
 * toast_helper.c
 *	  实现 varlena 属性的压缩或外存储的表 AM 的辅助函数。
 *
 * Copyright (c) 2000-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/access/table/toast_helper.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/detoast.h"
#include "access/table.h"
#include "access/toast_helper.h"
#include "access/toast_internals.h"
#include "catalog/pg_type_d.h"


/*
 * 准备对一个元组进行 TOAST。
 *
 * tupleDesc、toast_values 和 toast_isnull 是必需参数；它们
 * 提供有关要进行 TOAST 的元组的必要细节。
 *
 * 对于新插入的元组，toast_oldvalues 和 toast_oldisnull 应该为 NULL；
 * 对于更新，它们应该描述现有元组。
 *
 * 所有这些数组的长度应等于 tupleDesc->natts。
 *
 * 返回时，toast_flags 和 toast_attr 将被初始化。
 * toast_flags 只是一个 uint8，但 toast_attr 是一个调用者提供的
 * 数组，其长度等于 tupleDesc->natts。调用者在调用此函数之前
 * 不需要对数组进行任何初始化。
 */
void toast_tuple_init(ToastTupleContext *fc_ttc)
{
	TupleDesc	fc_tupleDesc = fc_ttc->ttc_rel->rd_att;
	int			fc_numAttrs = fc_tupleDesc->natts;
	int			fc_i;

	fc_ttc->ttc_flags = 0;

	for (fc_i = 0; fc_i < fc_numAttrs; fc_i++)
	{
		Form_pg_attribute fc_att = TupleDescAttr(fc_tupleDesc, fc_i);
		struct varlena *fc_old_value;
		struct varlena *fc_new_value;

		fc_ttc->ttc_attr[fc_i].tai_colflags = 0;
		fc_ttc->ttc_attr[fc_i].tai_oldexternal = NULL;
		fc_ttc->ttc_attr[fc_i].tai_compression = fc_att->attcompression;

		if (fc_ttc->ttc_oldvalues != NULL)
		{
			/*
			 * 对于 UPDATE 获取该属性的旧值和新值
			 */
			fc_old_value =
				(struct varlena *) DatumGetPointer(fc_ttc->ttc_oldvalues[fc_i]);
			fc_new_value =
				(struct varlena *) DatumGetPointer(fc_ttc->ttc_values[fc_i]);

			/*
			 * 如果旧值存储在磁盘上，检查它是否已更改，以便
			 * 我们稍后需要删除它。
			 */
			if (fc_att->attlen == -1 && !fc_ttc->ttc_oldisnull[fc_i] &&
				VARATT_IS_EXTERNAL_ONDISK(fc_old_value))
			{
				if (fc_ttc->ttc_isnull[fc_i] ||
					!VARATT_IS_EXTERNAL_ONDISK(fc_new_value) ||
					memcmp((char *) fc_old_value, (char *) fc_new_value,
						   VARSIZE_EXTERNAL(fc_old_value)) != 0)
				{
					/*
					 * 更新后不再需要旧的外部存储值
					 */
					fc_ttc->ttc_attr[fc_i].tai_colflags |= TOASTCOL_NEEDS_DELETE_OLD;
					fc_ttc->ttc_flags |= TOAST_NEEDS_DELETE_OLD;
				}
				else
				{
					/*
					 * 该属性在此更新中未更改，因此我们在新
					 * 元组中重用对旧值的原始引用。
					 */
					fc_ttc->ttc_attr[fc_i].tai_colflags |= TOASTCOL_IGNORE;
					continue;
				}
			}
		}
		else
		{
			/*
			 * 对于 INSERT 只需获取新值
			 */
			fc_new_value = (struct varlena *) DatumGetPointer(fc_ttc->ttc_values[fc_i]);
		}

		/*
		 * 处理 NULL 属性
		 */
		if (fc_ttc->ttc_isnull[fc_i])
		{
			fc_ttc->ttc_attr[fc_i].tai_colflags |= TOASTCOL_IGNORE;
			fc_ttc->ttc_flags |= TOAST_HAS_NULLS;
			continue;
		}

		/*
		 * 现在查看 varlena 属性
		 */
		if (fc_att->attlen == -1)
		{
			/*
			 * 如果表的属性始终标记为 PLAIN，则强制执行此标记。
			 */
			if (fc_att->attstorage == TYPSTORAGE_PLAIN)
				fc_ttc->ttc_attr[fc_i].tai_colflags |= TOASTCOL_IGNORE;

			/*
			 * 我们在上面处理了 UPDATE，因此我们在元组中找到的
			 * 任何外部值必须是其他人的，无法重用
			 * （这包括不在行内的内存数据的情况）。
			 * 将其取回（无需解压缩，除非我们强制
			 * PLAIN 存储）。如有必要，我们将在下面将其作为一个新的
			 * 外部值推出。
			 */
			if (VARATT_IS_EXTERNAL(fc_new_value))
			{
				fc_ttc->ttc_attr[fc_i].tai_oldexternal = fc_new_value;
				if (fc_att->attstorage == TYPSTORAGE_PLAIN)
					fc_new_value = detoast_attr(fc_new_value);
				else
					fc_new_value = detoast_external_attr(fc_new_value);
				fc_ttc->ttc_values[fc_i] = PointerGetDatum(fc_new_value);
				fc_ttc->ttc_attr[fc_i].tai_colflags |= TOASTCOL_NEEDS_FREE;
				fc_ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE);
			}

			/*
			 * 记住此属性的大小
			 */
			fc_ttc->ttc_attr[fc_i].tai_size = VARSIZE_ANY(fc_new_value);
		}
		else
		{
			/*
			 * 不是 varlena 属性，始终使用普通存储
			 */
			fc_ttc->ttc_attr[fc_i].tai_colflags |= TOASTCOL_IGNORE;
		}
	}
}

/*
 * 找到满足特定标准的最大 varlena 属性。
 *
 * 相关列不得标记为 TOASTCOL_IGNORE，并且，如果 
 * for_compression 标志作为 true 传递，则它也不得标记为 
 * TOASTCOL_INCOMPRESSIBLE。
 *
 * 如果 check_main 为 false，则列必须具有 attstorage EXTERNAL 或 EXTENDED；
 * 如果 check_main 为 true，则必须具有 attstorage MAIN。
 *
 * 列的最小大小必须为 MAXALIGN(TOAST_POINTER_SIZE)；
 * 如果不是，通过压缩它无法预期获得任何好处。
 *
 * 返回值是最大合适列的索引，或
 * -1 如果没有合适的列。
 */
int toast_tuple_find_biggest_attribute(ToastTupleContext *fc_ttc,
								   bool fc_for_compression, bool fc_check_main)
{
	TupleDesc	fc_tupleDesc = fc_ttc->ttc_rel->rd_att;
	int			fc_numAttrs = fc_tupleDesc->natts;
	int			fc_biggest_attno = -1;
	int32		fc_biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
	int32		fc_skip_colflags = TOASTCOL_IGNORE;
	int			fc_i;

	if (fc_for_compression)
		fc_skip_colflags |= TOASTCOL_INCOMPRESSIBLE;

	for (fc_i = 0; fc_i < fc_numAttrs; fc_i++)
	{
		Form_pg_attribute fc_att = TupleDescAttr(fc_tupleDesc, fc_i);

		if ((fc_ttc->ttc_attr[fc_i].tai_colflags & fc_skip_colflags) != 0)
			continue;
		if (VARATT_IS_EXTERNAL(DatumGetPointer(fc_ttc->ttc_values[fc_i])))
			continue;			/* 不会发生，toast_action 将是 PLAIN */
		if (fc_for_compression &&
			VARATT_IS_COMPRESSED(DatumGetPointer(fc_ttc->ttc_values[fc_i])))
			continue;
		if (fc_check_main && fc_att->attstorage != TYPSTORAGE_MAIN)
			continue;
		if (!fc_check_main && fc_att->attstorage != TYPSTORAGE_EXTENDED &&
			fc_att->attstorage != TYPSTORAGE_EXTERNAL)
			continue;

		if (fc_ttc->ttc_attr[fc_i].tai_size > fc_biggest_size)
		{
			fc_biggest_attno = fc_i;
			fc_biggest_size = fc_ttc->ttc_attr[fc_i].tai_size;
		}
	}

	return fc_biggest_attno;
}

/*
 * 尝试对一个属性进行压缩。
 *
 * 如果我们发现该属性不可压缩，则将其标记为不可压缩。
 */
void toast_tuple_try_compression(ToastTupleContext *fc_ttc, int fc_attribute)
{
	Datum	   *fc_value = &fc_ttc->ttc_values[fc_attribute];
	Datum		fc_new_value;
	ToastAttrInfo *fc_attr = &fc_ttc->ttc_attr[fc_attribute];

	fc_new_value = toast_compress_datum(*fc_value, fc_attr->tai_compression);

	if (DatumGetPointer(fc_new_value) != NULL)
	{
		/* 成功的压缩 */
		if ((fc_attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0)
			pfree(DatumGetPointer(*fc_value));
		*fc_value = fc_new_value;
		fc_attr->tai_colflags |= TOASTCOL_NEEDS_FREE;
		fc_attr->tai_size = VARSIZE(DatumGetPointer(*fc_value));
		fc_ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE);
	}
	else
	{
		/* 不可压缩，在后续的压缩过程中忽略 */
		fc_attr->tai_colflags |= TOASTCOL_INCOMPRESSIBLE;
	}
}

/*
 * 将一个属性移动到外部存储。
 */
void toast_tuple_externalize(ToastTupleContext *fc_ttc, int fc_attribute, int fc_options)
{
	Datum	   *fc_value = &fc_ttc->ttc_values[fc_attribute];
	Datum		fc_old_value = *fc_value;
	ToastAttrInfo *fc_attr = &fc_ttc->ttc_attr[fc_attribute];

	fc_attr->tai_colflags |= TOASTCOL_IGNORE;
	*fc_value = toast_save_datum(fc_ttc->ttc_rel, fc_old_value, fc_attr->tai_oldexternal,
							  fc_options);
	if ((fc_attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0)
		pfree(DatumGetPointer(fc_old_value));
	fc_attr->tai_colflags |= TOASTCOL_NEEDS_FREE;
	fc_ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE);
}

/*
 * 在一个元组被执行 TOAST 后执行适当的清理。
 */
void toast_tuple_cleanup(ToastTupleContext *fc_ttc)
{
	TupleDesc	fc_tupleDesc = fc_ttc->ttc_rel->rd_att;
	int			fc_numAttrs = fc_tupleDesc->natts;

	/*
	 * 释放分配的临时值
	 */
	if ((fc_ttc->ttc_flags & TOAST_NEEDS_FREE) != 0)
	{
		int			fc_i;

		for (fc_i = 0; fc_i < fc_numAttrs; fc_i++)
		{
			ToastAttrInfo *fc_attr = &fc_ttc->ttc_attr[fc_i];

			if ((fc_attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0)
				pfree(DatumGetPointer(fc_ttc->ttc_values[fc_i]));
		}
	}

	/*
	 * 从旧元组中删除外部值
	 */
	if ((fc_ttc->ttc_flags & TOAST_NEEDS_DELETE_OLD) != 0)
	{
		int			fc_i;

		for (fc_i = 0; fc_i < fc_numAttrs; fc_i++)
		{
			ToastAttrInfo *fc_attr = &fc_ttc->ttc_attr[fc_i];

			if ((fc_attr->tai_colflags & TOASTCOL_NEEDS_DELETE_OLD) != 0)
				toast_delete_datum(fc_ttc->ttc_rel, fc_ttc->ttc_oldvalues[fc_i], false);
		}
	}
}

/*
 * 检查外部存储属性并从二级关系中删除它们。
 */
void toast_delete_external(Relation fc_rel, Datum *fc_values, bool *fc_isnull,
					  bool fc_is_speculative)
{
	TupleDesc	fc_tupleDesc = fc_rel->rd_att;
	int			fc_numAttrs = fc_tupleDesc->natts;
	int			fc_i;

	for (fc_i = 0; fc_i < fc_numAttrs; fc_i++)
	{
		if (TupleDescAttr(fc_tupleDesc, fc_i)->attlen == -1)
		{
			Datum		fc_value = fc_values[fc_i];

			if (fc_isnull[fc_i])
				continue;
			else if (VARATT_IS_EXTERNAL_ONDISK(PointerGetDatum(fc_value)))
				toast_delete_datum(fc_rel, fc_value, fc_is_speculative);
		}
	}
}
